package com.zxq.ai.infrastructure.gateway;

public class RedisScript {

    /**
     * 1   key 已经过期，现在是被incr操作所创建的
     * 2   key 没有过期，只是初始值是一个 负数((step 的整数倍))，刚好涨到了 step 这个值上来。
     * 对于第一种情况 ，需要设置过期时间， 第二种情况则不需要
     * ( 优点 : 大部分情况下只需要执行 一条命令
     * 不足 : 该值为 0 ,或者 被加到 0 的时候, 在初始无过期时间的情况下,会被设置过期时间)
     * KEYS[1] key
     * ARGV[1] step
     * ARGV[2] 过期时间
     */
    public static final String INCRBY_1 = "local current = redis.call('incrBy',KEYS[1],ARGV[1]);" +
            " if (current == tonumber(ARGV[1]) and ARGV[2] > 0) then" +
            " local t = redis.call('ttl',KEYS[1]);" +
            " if t == -1 then " +
            " redis.call('expire',KEYS[1],ARGV[2])" +
            " end;" +
            " end;" +
            " return current";

    public static final String INCRBY_1_EXPIRE = "local current = redis.call('incrBy',KEYS[1],ARGV[1]);" +
            " if current == tonumber(ARGV[1]) then" +
            " local t = redis.call('ttl',KEYS[1]);" +
            " if t == -1 then " +
            " redis.call('expire',KEYS[1],ARGV[2])" +
            " end;" +
            " end;" +
            " return current";

    /**
     * 优缺点同上
     * KEYS[1] key
     * ARGV[1] step
     * ARGV[2] 过期时间
     */
    public static final String DECRBY_1 = "local current = redis.call('decrBy',KEYS[1],ARGV[1]);" +
            " if (-current == tonumber(ARGV[1]) and ARGV[2] > 0)then" +
            " local t = redis.call('ttl',KEYS[1]);" +
            " if t == -1 then " +
            " redis.call('expire',KEYS[1],ARGV[2])" +
            " end;" +
            " end;" +
            " return current";

    public static final String DECRBY_1_EXPIRE = "local current = redis.call('decrBy',KEYS[1],ARGV[1]);" +
            " if -current == tonumber(ARGV[1])then" +
            " local t = redis.call('ttl',KEYS[1]);" +
            " if t == -1 then " +
            " redis.call('expire',KEYS[1],ARGV[2])" +
            " end;" +
            " end;" +
            " return current";

    /**
     * 优点,能够准确的设置过期时间,并且能够设置初始值
     * 缺点,每次需要执行 2 个命令
     * (测试中未发现 exists 命令后 key 过期导致第二步调用 incrBy 操作后 key过期时间为 -1 的情况)
     * <p>
     * KEYS[1]  key;
     * ARGV[1]  过期时间
     * ARGV[2]  defaultValue
     * ARGV[3]  step
     */
    public static final String INCRBY_3 = "local ex = redis.call('exists',KEYS[1]);" +
            " if ex == 0 then " +
            "   if ARGV[1] > 0 then redis.call('setex',KEYS[1],ARGV[1],ARGV[2]+1)" +
            "   else redis.call('set',KEYS[1],ARGV[2]+ARGV[3]) end;" +
            "   return ARGV[2]+ARGV[3] " +
            " else" +
            "   return redis.call('incrBy',KEYS[1],ARGV[3])" +
            " end";

    public static final String INCRBY_3_PERSIST = "local ex = redis.call('exists',KEYS[1]);" +
            " if ex == 0 then " +
            "   redis.call('set',KEYS[1],ARGV[2]+ARGV[3])" +
            " return ARGV[2]+ARGV[3] " +
            " else" +
            "   return redis.call('incrBy',KEYS[1],ARGV[3])" +
            " end";

    public static final String INCRBY_3_EXPIRE = "local ex = redis.call('exists',KEYS[1]);" +
            " if ex == 0 then " +
            "   redis.call('setex',KEYS[1],ARGV[1],ARGV[2]+ARGV[3])" +
            "   return ARGV[2]+ARGV[3] " +
            " else" +
            "   return redis.call('incrBy',KEYS[1],ARGV[3])" +
            " end";

    /**
     * 优缺点同上
     * KEYS[1]  key;
     * ARGV[1]  过期时间
     * ARGV[2]  defaultValue
     * ARGV[3]  step
     */

    public static final String DECRBY_3_PERSIST = "local ex = redis.call('exists',KEYS[1]);" +
            " if ex == 0 then" +
            "   redis.call('set',KEYS[1],ARGV[2]-ARGV[3])" +
            "   return ARGV[2]-ARGV[3] " +
            " else" +
            " return redis.call('decrBy',KEYS[1],ARGV[3])" +
            " end";

    public static final String DECRBY_3_EXPIRE = "local ex = redis.call('exists',KEYS[1]);" +
            " if ex == 0 then" +
            "   redis.call('setex',KEYS[1],ARGV[1],ARGV[2]-ARGV[3])" +
            "   return ARGV[2]-ARGV[3]" +
            " else" +
            "   return redis.call('decrBy',KEYS[1],ARGV[3])" +
            " end";

    public static final String PFADD = "local ex = redis.call('exists',KEYS[1]);" +
            " local t = table.remove(ARGV,1);" +
            " local rt = redis.call('pfadd',KEYS[1],unpack(ARGV));" +
            " if ex==0 then redis.call('expire',KEYS[1],t) end;" +
            " return rt";

    /**
     * compareAndSet with expire seconds:
     * KEYS[1] : key
     * ARGV[1] : expected value
     * ARGV[2] : try set value
     * ARGV[3] : try set expires
     */
    public static final String COMPARE_AND_SET_WITH_EXPIRE = "local cV = redis.call('get', KEYS[1]); "
            + "if cV == ARGV[1] "
            + "or (tonumber(ARGV[1]) == 0 and cV == false) then "
            + "redis.call('setex', KEYS[1], ARGV[3], ARGV[2]); "
            + "return 1 "
            + "else "
            + "return 0 "
            + "end";

    public static final byte[] COMPARE_AND_SET_WITH_EXPIRE_BYTES = SafeEncoder.encode(COMPARE_AND_SET_WITH_EXPIRE);

    public static final String COMPARE_AND_SET = "local cV = redis.call('get', KEYS[1]); "
            + "if cV == ARGV[1] "
            + "or (tonumber(ARGV[1]) == 0 and cV == false) then "
            + "redis.call('set', KEYS[1], ARGV[2]); "
            + "return 1 "
            + "else "
            + "return 0 "
            + "end";

    public static final byte[] COMPARE_AND_SET_BYTES = SafeEncoder.encode(COMPARE_AND_SET);

    public static final String COMPARE_AND_DEL = "local cV = redis.call('get', KEYS[1]); "
            + "if cV == ARGV[1] "
            + "or (tonumber(ARGV[1]) == 0 and cV == false) then "
            + " redis.call('del', KEYS[1]); "
            + "return 1 "
            + "else "
            + "return 0 "
            + "end";

    public static final byte[] COMPARE_AND_DEL_BYTES = SafeEncoder.encode(COMPARE_AND_DEL);

    /**
     * KEYS[1]  key;
     * ARGV[1]  field
     * ARGV[2]  expect
     * ARGV[3]  update
     * ARGV[4]  expireInSeconds
     */
    public static final String H_COMPARE_AND_SET = "local cV = redis.call('hget',KEYS[1],ARGV[1]); "
            + "if cV == ARGV[2] then "
            + " redis.call('hset',KEYS[1],ARGV[1],ARGV[3]); "
            + " return 1 "
            + "else "
            + "return 0 "
            + "end";

    public static final byte[] H_COMPARE_AND_SET_BYTES = SafeEncoder.encode(H_COMPARE_AND_SET);

    public static final String H_COMPARE_AND_SET_WITH_EXPIRE = "local cV = redis.call('hget',KEYS[1],ARGV[1]); "
            + "if cV == ARGV[2] then "
            + " redis.call('hset',KEYS[1],ARGV[1],ARGV[3]); "
            + " redis.call('expire',KEYS[1],ARGV[4]);"
            + "return 1 "
            + "else "
            + "return 0 "
            + "end";

    public static final byte[] H_COMPARE_AND_SET_WITH_EXPIRE_BYTES = SafeEncoder.encode(H_COMPARE_AND_SET_WITH_EXPIRE);

    public static final String HSETNX_WITH_EXPIRE = "local cV = redis.call('hsetnx',KEYS[1],ARGV[1],ARGV[2]); "
            + "if cV == 1 then"
            + " redis.call('expire',KEYS[1],ARGV[3]);"
            + "return 1 "
            + "else "
            + "return 0 "
            + "end";

    public static final byte[] HSETNX_WITH_EXPIRE_BYTES = SafeEncoder.encode(HSETNX_WITH_EXPIRE);


    /**
     * KEYS[1]  key;
     * ARGV[1]  field
     * ARGV[2]  value
     * ARGV[3]  expireInSeconds
     */
    public static final String H_SET_AND_EXPIRE = "local v=redis.call('hset',KEYS[1],ARGV[1],ARGV[2]);"
            + "redis.call('expire',KEYS[1],ARGV[3]);return v;";

    public static final byte[] H_SET_AND_EXPIRE_BYTES = SafeEncoder.encode(H_SET_AND_EXPIRE);

    /**
     * KEYS[1]  key;
     * ARGV[1]  score
     * ARGV[2]  member
     * ARGV[3]  expireInSeconds
     */
    public static final String ZADD_AND_EXPIRE = "local v=redis.call('zadd',KEYS[1],ARGV[1],ARGV[2]);"
            + "redis.call('expire',KEYS[1],ARGV[3]);return v;";

    public static final byte[] ZADD_AND_EXPIRE_BYTES = SafeEncoder.encode(ZADD_AND_EXPIRE);

    /**
     * KEYS[1]  key;
     * ARGV[1]  members
     * ARGV[2]  expireInSeconds
     */
    public static final String SADD_AND_EXPIRE = "local v= redis.call('sadd',KEYS[1],ARGV[1]);"
            + "redis.call('expire',KEYS[1],ARGV[2]);return v;";

    public static final byte[] SADD_AND_EXPIRE_BYTES = SafeEncoder.encode(SADD_AND_EXPIRE);


    public static final String GETSET_AND_EXPIRE = "local v=redis.call('getset',KEYS[1],ARGV[1]);" +
            "redis.call('expire',KEYS[1],ARGV[2]);return v;";

    public static final byte[] GETSET_AND_EXPIRE_BYTES = SafeEncoder.encode(GETSET_AND_EXPIRE);


    /**
     * ARGV[1]  过期时间
     * ARGV[2]  上界
     * ARGV[3]  step
     * ARGV[4]  初始值
     */
    public static final String INCRBY_WITH_UPPER_BOUND_PERSIST = "local ex = redis.call('exists',KEYS[1]);" +
            " if ex == 0 then " +

            "   if tonumber(ARGV[2]) >= tonumber(ARGV[3]) + tonumber(ARGV[4]) then" +
            "       redis.call('set',KEYS[1],ARGV[3] + ARGV[4])" +
            "       return string.format('%d:%d', ARGV[3] + ARGV[4],ARGV[3])" +
            "   else" +
            "       redis.call('set',KEYS[1],ARGV[4])" +
            "       return string.format('%d:0', ARGV[4])" +
            "   end" +

            " else " +
            "   local v = redis.call('incrby',KEYS[1],ARGV[3]);" +
            "   if tonumber(ARGV[2]) >= v then" +
            "       return string.format('%d:%d',v,ARGV[3])" +
            "   else " +
            "       redis.call('decrBy',KEYS[1],ARGV[3]);" +
            "       return  string.format('%d:0',v - ARGV[3])" +
            "   end" +
            " end";

    public static final String INCRBY_WITH_UPPER_BOUND_EXPIRE = "local ex = redis.call('exists',KEYS[1]);" +
            " if ex == 0 then " +
            "   if tonumber(ARGV[2]) >= tonumber(ARGV[3]) + tonumber(ARGV[4])  then" +
            "       if tonumber(ARGV[1]) > 0 then " +
            "           redis.call('setex',KEYS[1],ARGV[1],ARGV[3] + ARGV[4])" +
            "       else " +
            "           redis.call('set',KEYS[1],ARGV[3] + ARGV[4])" +
            "       end" +
            "       return string.format('%d:%d',ARGV[3] + ARGV[4],ARGV[3])" +
            "   else" +
            "       if tonumber(ARGV[1]) > 0 then " +
            "           redis.call('setex',KEYS[1],ARGV[1],ARGV[4])" +
            "       else " +
            "           redis.call('set',KEYS[1],ARGV[4])" +
            "       end" +
            "       return string.format('%d:0', ARGV[4])" +
            "   end" +
            " else " +
            "   local v = redis.call('incrby',KEYS[1],ARGV[3]);" +
            "   if tonumber(ARGV[2]) >= v then" +
            "       return string.format('%d:%d',v,ARGV[3])" +
            "   else " +
            "       redis.call('decrBy',KEYS[1],ARGV[3]);" +
            "       return  string.format('%d:0',v - ARGV[3])" +
            "   end" +
            " end";

    public static final byte[] INCRBY_WITH_UPPER_BOUND_EXPIRE_BYTES = SafeEncoder.encode(INCRBY_WITH_UPPER_BOUND_EXPIRE);


    public static final String DECRBY_WITH_LOWER_BOUND_PERSIST = "local ex = redis.call('exists',KEYS[1]);" +
            " if ex == 0 then " +

            "   if tonumber(ARGV[2]) <= tonumber(ARGV[4]) - tonumber(ARGV[3])  then" +
            "       redis.call('set',KEYS[1],ARGV[4] - ARGV[3])" +
            "       return ARGV[4] - ARGV[3]" +
            "   else" +
            "       return ARGV[4]" +
            "   end" +

            " else " +
            "   local v = redis.call('decrBy',KEYS[1],ARGV[3]);" +
            "   if tonumber(ARGV[2]) <= v then" +
            "       return v" +
            "   else " +
            "       return redis.call('incrBy',KEYS[1],ARGV[3]);" +
            "   end" +
            " end";

    public static final String DECRBY_WITH_LOWER_BOUND_EXPIRE = "local ex = redis.call('exists',KEYS[1]);" +
            " if ex == 0 then " +
            "   if tonumber(ARGV[2]) <= tonumber(ARGV[4]) - tonumber(ARGV[3])  then" +
            "       if tonumber(ARGV[1]) > 0 then " +
            "           redis.call('setex',KEYS[1],ARGV[1],ARGV[4] - ARGV[3])" +
            "       else" +
            "           redis.call('set',KEYS[1],ARGV[4] - ARGV[3])" +
            "       end" +
            "       return string.format('%d:%d',ARGV[4] - ARGV[3],ARGV[3])" +
            "   else" +
            "       if tonumber(ARGV[1]) > 0 then " +
            "           redis.call('setex',KEYS[1],ARGV[1],ARGV[4])" +
            "       else " +
            "           redis.call('set',KEYS[1],ARGV[4])" +
            "       end" +
            "       return string.format('%d:0', ARGV[4])" +
            "   end" +

            " else " +
            "   local v = redis.call('decrBy',KEYS[1],ARGV[3]);" +
            "   if tonumber(ARGV[2]) <= v then" +
            "       return string.format('%d:%d',v,ARGV[3])" +
            "   else " +
            "       redis.call('incrBy',KEYS[1],ARGV[3]);" +
            "       return  string.format('%d:0',v + ARGV[3])" +
            "   end" +
            " end";

    public static final byte[] DECRBY_WITH_LOWER_BOUND_EXPIRE_BYTES = SafeEncoder.encode(DECRBY_WITH_LOWER_BOUND_EXPIRE);

    public static final String ZPOPMIN = "local key = KEYS[1];" +
            " local result = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES');" +
            " local member = result[1];" +
            " if member then " +
            "   redis.call('ZREM', key, member)" +
            "   return result" +
            " else " +
            "   return nil " +
            " end";

    public static final byte[] ZPOPMIN_BYTES = SafeEncoder.encode(ZPOPMIN);
}
